require "open3"

module Rubtools
  class Recipe
    attr_accessor :available_methods, :methods_hidden, :options, :config
    @methods_hidden = false

    def self.register_methods methods
      @available_methods = methods
    end

    def self.available_methods
      @available_methods ? @available_methods : []
    end

    def self.hide_methods
      @methods_hidden = true
    end

    def self.are_methods_hidden?
      @methods_hidden
    end

    ## Override new method to initialize recipes objects without config and options
    #
    def self.new config, options, *args, &blk
      object = allocate
      object.config = config
      object.options = options
      object.instance_eval { initialize( *args, &blk ) }
      object
    end

    # Exec without output
    #
    def exec_without_output cmd, &block
      success "$ " + cmd if @options[:verbose]

      Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
        while line = stdout.gets
          verbose line if @options[:verbose]
        end

        while line = stderr.gets
          error line if @options[:verbose]
        end

        exit_status = wait_thr.value

        if block
          block.call exit_status
        end

        return stdout
      end

      return nil
    end

    # Exec with output
    #
    def exec cmd, &block
      success "$ " + cmd if @options[:verbose]

      Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
        while line = stdout.gets
          verbose line
        end

        while line = stderr.gets
          error line
        end

        exit_status = wait_thr.value

        if block
          block.call exit_status
        end

        return stdout
      end

      return nil
    end
      
    ## Get operating system name
    #
    def os
      return (
        host_os = RbConfig::CONFIG['host_os']
        case host_os
        when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
          :windows
        when /darwin|mac os/
          :osx
        when /linux/
          :linux
        when /solaris|bsd/
          :unix
        else
          raise "Unknown os: #{host_os.inspect}"
        end
      )
    end

    ## Cross-platform way of finding an executable in the $PATH.
    #
    def which cmd
      exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
      ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
        exts.each do |ext|
          exe = File.join(path, "#{cmd}#{ext}")
          return exe if File.executable? exe
        end
      end
      return nil
    end

    ## Helper to symlink
    #
    def symlink source, dest
      FileUtils.rm dest, verbose: true if File.exists?(dest)
      if File.exists?(source)
        FileUtils.ln_s source, dest, verbose: true, force: true 
      else
        raise FileNotFoundException.new("File #{source} not found")
      end
    end

    # Print success arguments
    #
    def success args
      Logger.success args
    end

    # Print errors arguments
    #
    def error args
      Logger.error args
    end

    # Print verbose arguments
    #
    def verbose args
      Logger.verbose args
    end

    # Print info arguments
    #
    def info args
      Logger.info args
    end
  end
end

